package com.apadog.study.wxpay;

import cn.hutool.core.util.StrUtil;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.apadog.study.wxpay.dto.WechatPayDTO;
import com.apadog.study.wxpay.dto.WechatPayNativeDTO;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.util.NonceUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Slf4j
public class WxPayService {

    @Value("${gzh.callback}")
    private String nativeCallback;


    /**
     * 拉起扫码支付
     * @param weChatPayNativeParams 拉起参数
     * */
    public Object nativePay(WeChatPayNativeParams weChatPayNativeParams) {

        try{

            TxPayConfig txPayConfig = TxPayConfig.builder().build();
            WxPayConfig wxPayConfig= new WxPayConfig(txPayConfig);

            com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
            // 【公众账号ID】APPID是商户公众号唯一标识
            prepayRequest.setAppid(txPayConfig.getAppid());
            // 【商户号】是由微信支付系统生成并分配给每个商户的唯一标识符，
            prepayRequest.setMchid(txPayConfig.getMchId());
            // 【商户订单号】商户系统内部订单号，要求6-32个字符内，只能是数字、大小写字母_-|* 且在同一个商户号下唯一。
            prepayRequest.setOutTradeNo("雪花id");
            // 【商品描述】商品信息描述，
            String desc = StrUtil.format("预存金额");
            prepayRequest.setDescription(desc);


            String backurl = StrUtil.format("{}/payApi/payNativeNotify",nativeCallback);
            prepayRequest.setNotifyUrl(backurl);
            // 【订单金额】订单金额信息
            com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
            // 【总金额】 订单总金额，单位为分，整型。 示例：1元应填写 100
            amount.setTotal(100);
            prepayRequest.setAmount(amount);

            //自定义参数
            prepayRequest.setAttach("");

            com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse prepay = wxPayConfig.nativePayService().prepay(prepayRequest);
            WechatPayNativeDTO wechatPayDto = new WechatPayNativeDTO();
            wechatPayDto.setUrl(prepay.getCodeUrl());
            wechatPayDto.setOrderId("雪花id");
            return wechatPayDto;
        } catch (HttpException e) { // 发送HTTP请求失败
            throw new RuntimeException("发送HTTP请求失败: " + e.getHttpRequest());
        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300，例如500
            throw new RuntimeException("服务返回状态异常: "+ e.getResponseBody());
        } catch (MalformedMessageException e) { // 服务返回成功，返回体类型不合法，或者解析返回体失败
            throw new RuntimeException("返回体类型不合法: "+ e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("调起支付异常: "+ e.getMessage());
        }
    }


    /**
     * js API 支付
     * @param param 预支付参数
     * @return 小程序唤起支付（金额已经确定了 输入密码的页面）
     */
    public WechatPayDTO jsPay(WechatPayReqParam param) {


        //先创建未提交的订单

        TxPayConfig txPayConfig = TxPayConfig.builder().build();
        WxPayConfig wxPayConfig= new WxPayConfig(txPayConfig);

        PrepayRequest prepayRequest = new PrepayRequest();
        // 【公众账号ID】APPID是商户公众号唯一标识
        prepayRequest.setAppid(txPayConfig.getAppid());
        // 【商户号】是由微信支付系统生成并分配给每个商户的唯一标识符，
        prepayRequest.setMchid(txPayConfig.getMchId());
        // 【商户订单号】商户系统内部订单号，要求6-32个字符内，只能是数字、大小写字母_-|* 且在同一个商户号下唯一。
        prepayRequest.setOutTradeNo("雪花id");
        // 【商品描述】商品信息描述，
        String desc = "描述信息";
        prepayRequest.setDescription(desc);
        /*
         *【商户回调地址】商户接收支付成功回调通知的地址
         * notify_url必须是以https:// 或 http:// 开头的完整全路径地址，
         * 并且确保URL中的域名和IP是外网可以访问的，
         * 不能填写localhost、127.0.0.1、192.168.x.x等本地或内网IP。
         * 特别说明：
         *          如果是http就在服务器的代理监听80端口
         *          如果是https就在服务器的代理监听443端口
         * 并且注意处理回调的时候controller中的接口 不要拦截 不要鉴权 否则微信调用不到。
         * 也可以给回调通知的IP做白名单，详细的IP 参考：https://pay.weixin.qq.com/doc/v3/merchant/4012075420
         */
        String backurl = StrUtil.format("{}/payApi/payNotify",nativeCallback);
        prepayRequest.setNotifyUrl(backurl);
        // 【订单金额】订单金额信息
        Amount amount = new Amount();
        // 【总金额】 订单总金额，单位为分，整型。 示例：1元应填写 100
        amount.setTotal(100);
        prepayRequest.setAmount(amount);
        // 【支付者信息】支付者信息
        Payer payer = new Payer();
        // 【用户标识】用户在商户appid下的唯一标识。下单前需获取到用户的OpenID。
        payer.setOpenid(param.getOpenId());
        prepayRequest.setPayer(payer);


        //自定义参数
        prepayRequest.setAttach("");

        // 调用下单方法，得到应答
        try {
            PrepayResponse prepay = wxPayConfig.jsapiService().prepay(prepayRequest);
            // 将 WechatPayDto 中的值返回小程序就可以唤起支付页面 也就是输入密码页面
            WechatPayDTO wechatPayDto = new WechatPayDTO();
            wechatPayDto.setAppid(txPayConfig.getAppid());
            wechatPayDto.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000));
            wechatPayDto.setNonceStr(NonceUtil.createNonce(32));
            wechatPayDto.setPrepayId("prepay_id=" + prepay.getPrepayId());
            wechatPayDto.setSignType("RSA");
            wechatPayDto.setPaySign(WeChatUtil.getSign(Stream.of(txPayConfig.getAppid(),
                            wechatPayDto.getTimeStamp(),
                            wechatPayDto.getNonceStr(),
                            wechatPayDto.getPrepayId()).collect(Collectors.joining("\n", "", "\n")),
                    txPayConfig.getKeyPath(),
                    txPayConfig.getMchSerialNo()));

            return wechatPayDto;
        } catch (HttpException e) { // 发送HTTP请求失败
            throw new RuntimeException("发送HTTP请求失败: " + e.getHttpRequest());
        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300，例如500
            throw new RuntimeException("服务返回状态异常: "+ e.getResponseBody());
        } catch (MalformedMessageException e) { // 服务返回成功，返回体类型不合法，或者解析返回体失败
            throw new RuntimeException("返回体类型不合法: "+ e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("调起支付异常: "+ e.getMessage());
        }

    }

    /**
     * 支付的回调
     * @param request request
     * @param response response
     * @return
     */
    public ResponseEntity payNotify(HttpServletRequest request, HttpServletResponse response) {
        Transaction transaction;
        try {
            WxPayConfig wxPayConfig= new WxPayConfig(TxPayConfig.builder().build());
            transaction = wxPayConfig.notificationParser().parse(WeChatUtil.handleNodifyRequestParam(request), Transaction.class);
            log.info("微信支付成功回调:{}",transaction);
            if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS){

                //响应微信
                return ResponseEntity.status(HttpStatus.OK).body(null);
            }
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        } catch (ValidationException | IOException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            log.error("微信支付成功回调:异常:{}",e.getMessage());
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
        }

    }

    /**
     * 扫码支付成功回调
     * */
    public ResponseEntity payNativeNotify(HttpServletRequest request, HttpServletResponse response) {
        Transaction transaction;
        try {
            WxPayConfig wxPayConfig= new WxPayConfig(TxPayConfig.builder().build());
            transaction = wxPayConfig.notificationParser().parse(WeChatUtil.handleNodifyRequestParam(request), Transaction.class);

            log.info("扫码支付成功回调:{}",transaction);
            if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS){

                //响应微信
                return ResponseEntity.status(HttpStatus.OK).body(null);
            }
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        } catch (ValidationException | IOException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            log.error("扫码支付成功回调:异常:{}",e.getMessage());
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
        }
    }

    /**
     * 申请退款
     * @param param
     * @return
     */

    public String payRefund(WechatPayRefundReqParam param) {

        WxPayConfig wxPayConfig= new WxPayConfig(TxPayConfig.builder().build());

        CreateRequest request = new CreateRequest();
        //设置退款金额 根据自己的实际业务自行填写
        AmountReq amountReq = new AmountReq();

        // 退款金额
        amountReq.setRefund(Long.valueOf(100));
        // 支付时的金额
        amountReq.setTotal(Long.valueOf(100));
        // 【退款币种】 符合ISO 4217标准的三位字母代码，固定传：CNY，代表人民币。
        amountReq.setCurrency("CNY");
        // 【金额信息】订单退款金额信息
        request.setAmount(amountReq);
        //退款备注
        request.setReason(param.getReason());
        // 【商户退款单号】 商户系统内部的退款单号，商户系统内部唯一
        request.setOutRefundNo("雪花id");//可以跟 支付成功用一个订单号
        // 【商户订单号】 商户下单时传入的商户系统内部订单号。订单号是自己内部业务生成的内部唯一
        //request.setTransactionId(txOrder.getTransactionId());
        request.setOutTradeNo("雪花id");
        //【退款结果回调url】退款的回调地址

        String backurl = StrUtil.format("{}/payApi/refundNotify",nativeCallback);
        request.setNotifyUrl(backurl);
        // 提交退款申请成功，。
        Refund refund;
        try {
            refund = wxPayConfig.refundService().create(request);
        }catch (ServiceException e){
            String httpResponseBody = e.getResponseBody();  // 假设有此方法获取响应体
            log.error("申请微信退款异常请联系管理员:{}",httpResponseBody);
            throw new RuntimeException("申请微信退款异常");
        }catch (Exception e){
            log.error("申请微信退款异常请联系管理员:{}",e.getMessage());
            throw new RuntimeException("申请微信退款未知异常");
        }
        // CreateTime 微信受理退款申请单的时间 退款是否成功不在这里判断，在退款回调中做处理。
        //更新订单退款信息

        return refund.getCreateTime();
    }

    /**
     * 退款的回调
     * 2.3、微信支付回调处理机制说明
     * 微信支付接收到商户的应答后，会根据应答结果做对应的逻辑处理：
     * 若商户应答回调接收成功，微信支付将不再重复发送该回调通知。若因网络或其他原因，商户收到了重复的回调通知，请按正常业务流程进行处理并应答。
     * 若商户应答回调接收失败，或超时(5s)未应答时，微信支付会按照（15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h）的频次重复发送回调通知，直至微信支付接收到商户应答成功，或达到最大发送次数（15次）
     * 所以 以防万一 最好在处理业务之前判断一下 是否已经处理了 如果已经出力了 就不在处理 直接响应微信就好了
     */
    public String refundNotify(HttpServletRequest request, HttpServletResponse response) {
        final HashMap<String, Object> map = new HashMap<>();
        try {

            WxPayConfig wxPayConfig= new WxPayConfig(TxPayConfig.builder().build());
            RefundNotification parse = wxPayConfig.notificationParser().parse(WeChatUtil.handleNodifyRequestParam(request), RefundNotification.class);
            log.info("微信支付退款回调:{}",parse);
            if (Status.SUCCESS.equals(parse.getRefundStatus())){
                // 订单号
                //处理退单逻辑

            } else {

            }
            response.setStatus(200);
            return null;
        } catch (IOException e) {
            // 通知验签失败
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "验签失败");
            e.printStackTrace();
            log.error("微信支付退款回调:异常:{}",e.getMessage());
            return JSON.toJSONString(map);
        }
    }






}
